/* $Id$ */

#include <nih/cdefs.h>

#include <nih/memory.h>

#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>

nih_oom_fatal_fnc *nih_oom_fatal = NULL;

struct nih_dump_cfg nih_dump_cfg_dflt = {
        7, ": ", 16, 2, " ", "  ", ' ', false
};

static char *alloc_dump_buf(struct nih_dump_cfg *cfg, size_t len);
static ssize_t sprint_dump_line(struct nih_dump_cfg *cfg, char *buf,
                                const void *ptr, size_t len);

inline char *alloc_dump_buf(struct nih_dump_cfg *cfg, size_t len)
{
        size_t asize;
        char *res;

        asize  = cfg->adwidth + strlen(cfg->adsep); /* address */
        asize += cfg->cols * 2;                     /* hex bytes */
                                                    /* group seps */
        asize += strlen(cfg->grsep) * ((cfg->cols + 1) / cfg->group - 1);
        asize += strlen(cfg->prisep) + cfg->cols;   /* printables */
        asize += 1;                                 /* new line or \0 */
        asize *= (len - 1) / cfg->cols + 1;         /* number of lines */

        res = malloc(asize);

        printf("allocated %i bytes\n", asize);
        return res;
}

inline ssize_t sprint_dump_line(struct nih_dump_cfg *cfg, char *buf,
                                const void *ptr, size_t len)
{
        char *b;
        char *pri;
        char *addr_f;
        char *byte_f;
        size_t i;
        char first_sav;

        first_sav = 0; /* make compiler shut up */
        b = buf;

        /* TODO: strip address not to overflow given number of characters */

        pri = b;
        pri += cfg->adwidth + strlen(cfg->adsep);
        pri += cfg->cols * 2;
        pri += strlen(cfg->grsep) * ((cfg->cols + 1) / cfg->group - 1);
        pri += strlen(cfg->prisep);

        if (cfg->upper) {
                addr_f = "%0*" PRIXPTR;
                byte_f = "%02" PRIX8;
        } else {
                addr_f = "%0*" PRIxPTR;
                byte_f = "%02" PRIx8;
        }

        b += sprintf(b, addr_f, (int) cfg->adwidth, (uintptr_t) ptr);
        b += sprintf(b, "%s", cfg->adsep);

        for (i = 0; i < cfg->cols; i++) {
                if (i + 1 <= len) {
                        unsigned char byte;

                        byte = ((unsigned char *) ptr)[i];

                        b += sprintf(b, byte_f, byte);
                        if (! isprint(byte))
                                byte = cfg->nonpri;
                        /* first byte gets overwriten by prisep's \0 */
                        if (i == 0)
                                first_sav = byte;
                        else
                                pri[i] = byte;
                } else {
                        b += sprintf(b, "%s", "  ");
                        pri[i] = ' ';
                }

                if (i + 1 < cfg->cols) {
                        if (((i + 1) % cfg->group) == 0) {
                                if (i + 1 < len)
                                        b += sprintf(b, "%s", cfg->grsep);
                                else
                                        b += sprintf(b, "%*s",
                                                     strlen(cfg->grsep), "");
                        }
                } else
                        b += sprintf(b, "%s", cfg->prisep);
        }
        pri[0] = first_sav;

        return (pri + len - buf);
}

ssize_t nih_memdump_own(struct nih_dump_cfg *cfg, char **buf,
                        const void *ptr, size_t len)
{
        char *b;
        size_t lines;
        size_t i;

        if (! cfg)
                cfg = &nih_dump_cfg_dflt;

        if (! buf || ! ptr) {
                errno = EINVAL;
                return -1;
        }

        if (len == 0) {
                *buf = strdup("");
                return 0;
        }

        if (! (*buf = alloc_dump_buf(cfg, len)))
            return -1;

        b = *buf;
        lines = (len - 1) / cfg->cols + 1;
        for (i = 0; i < lines; i++) {
                ssize_t res;
                size_t line_len;

                line_len = cfg->cols;
                if (i + 1 == lines && len % cfg->cols != 0)
                        line_len = len % cfg->cols;

                res = sprint_dump_line(cfg, b, ptr, line_len);

                if (res < 0) {
                        free(*buf);
                        return -1;
                }
                b += res;

                if (i + 1 < lines)
                        *b = '\n';
                else
                        *b = '\0';
                b++;

                ptr = (void *) ((char *) ptr + cfg->cols);
        }
                
        printf("used %i bytes\n", b - *buf);
        return (b - *buf);
}

inline ssize_t nih_memdump(char **buf, const void *ptr, size_t dump_len)
{
        return nih_memdump_own(NULL, buf, ptr, dump_len);
}

